package editors.objectedit;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

import game.engine.Empty;
import game.engine.GSync;
import game.engine.GSync.SyncType;
import game.engine.TriggerArea;
import game.objects.GODynamic;
import game.objects.GameObject;
import graphics.objects.GAtmosphereLight;
import graphics.objects.GCircle;
import graphics.objects.GPointLight;
import graphics.objects.GPolygon;
import graphics.objects.GRect;
import graphics.objects.GraphicsObject;
import graphics.system.Color;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import main.Config;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import physics.Body;
import physics.joints.JDistanceJoint;
import physics.joints.JSpring;
import physics.joints.Joint;
import physics.shapes.Circle;
import physics.shapes.Polygon;
import physics.shapes.Shape;
import tools.Help;
import tools.Log;
import tools.Point;
public class LoadObject {
	// The map that (old id of a body, new body) as key value pairs
	// This is needed because the bodies' ids will change when the game object
	// gets loaded and added, so the
	// graphics objects synchronization structs still point to the correct body.
	public static TreeMap<Integer, Body> bodyIdAssign;
	private static TreeMap<Integer, Joint> jointIdAssign;
	public static GameObject load(String fileName, GameObject go) {
		return load(fileName, go, (BoundingBox) null);
	}
	public static GameObject load(String fileName, GameObject go, Point pos) {
		fileName = Config.MODELFOLDER + fileName;
		bodyIdAssign = new TreeMap<Integer, Body>();
		jointIdAssign = new TreeMap<Integer, Joint>();
		Document doc = getDocumentElement(fileName);
		if (doc != null) {
			Element gameObjectElement = doc.getDocumentElement();
			BoundingBox oldBox = readBoundingBox(gameObjectElement);
			PointTransformer form = null;
			form = new PointTransformer(oldBox, oldBox.move(pos.mul(-1))); // load with identical size but moved
			GameObject o = loadGameObject(gameObjectElement, go, form);
			return o;
		}
		return null;
	}
	public static GameObject load(String fileName, GameObject go, BoundingBox box) {
		fileName = Config.MODELFOLDER + fileName;
		bodyIdAssign = new TreeMap<Integer, Body>();
		jointIdAssign = new TreeMap<Integer, Joint>();
		Document doc = getDocumentElement(fileName);
		if (doc != null) {
			Element gameObjectElement = doc.getDocumentElement();
			BoundingBox oldBox = readBoundingBox(gameObjectElement);
			PointTransformer form = null;
			if (box != null)
				form = new PointTransformer(box, oldBox);
			else
				form = new PointTransformer(oldBox, oldBox); // load with identical size
			GameObject o = loadGameObject(gameObjectElement, go, form);
			return o;
		}
		return null;
	}
	public static GameObject loadCopy(Document doc, GameObject go, Point pos) {
		bodyIdAssign = new TreeMap<Integer, Body>();
		jointIdAssign = new TreeMap<Integer, Joint>();
		if (doc != null) {
			Element gameObjectElement = doc.getDocumentElement();
			BoundingBox oldBox = readBoundingBox(gameObjectElement);
			PointTransformer form = null;
			form = new PointTransformer(oldBox, oldBox.move(pos.mul(-1))); // load with identical size but moved
			GameObject o = loadGameObject(gameObjectElement, go, form);
			return o;
		}
		return null;
	}
	private static BoundingBox readBoundingBox(Element gameObjectElement) {
		String s = gameObjectElement.getAttribute("box");
		String[] splitted = s.split(" ");
		return new BoundingBox(Float.valueOf(splitted[0]), Float.valueOf(splitted[1]), Float.valueOf(splitted[2]), Float.valueOf(splitted[3]));
	}
	private static GameObject loadGameObject(Element ge, GameObject go, PointTransformer form) {
		NodeList children = ge.getChildNodes();
		int postbodyoffset = go.numBodies;
		for (int i = 0; i < children.getLength(); i++) {
			process(children.item(i), go, form);
		}
		postProcess(go, postbodyoffset);
		return go;
	}
	/**
	 * reassigns nonColBody id
	 * 
	 * @param go
	 * @param postbodyoffset
	 */
	private static void postProcess(GameObject go, int postbodyoffset) {
		for (int i = postbodyoffset; i < go.numBodies; i++) {
			Body b = go.getBody(i);
			for (int c = 0; c < b.nonCollidingBodies.size(); c++) {
				Body bc = bodyIdAssign.get(b.nonCollidingBodies.get(c));
				if (bc != null) {
					b.nonCollidingBodies.set(c, bc.id);
				}
				else {
					Help.p("not such body:" + b.nonCollidingBodies.get(c));
				}
			}
		}
	}
	private static void process(Node item, GameObject go, PointTransformer form) {
		String name = item.getNodeName();
		if ("body".equals(name)) {
			Body b = processBody(item, go, form);
			go.addBody(b);
			NamedNodeMap attributes = item.getAttributes();
			String n = getAttr(attributes, "name");
			if (!n.equals(""))
				go.setName(b, n);
			return;
		}
		GraphicsObject o = null;
		if ("GRect".equals(name)) {
			o = new GRect(new Point(), 0, 0, 0, Color.black); // Dummy object
		}
		else if ("GPolygon".equals(name)) {
			o = new GPolygon();
		}
		else if ("GCircle".equals(name)) {
			o = new GCircle(new Point(), 0.0f, 0.0f, Color.black); // Dummy
																	// object
		}
		else if ("GPointLight".equals(name)) {
			o = new GPointLight(new Point(), 0.0f, Color.black); // Dummy
			// object
		}
		else if ("GAtmosphereLight".equals(name)) {
			o = new GAtmosphereLight(new Point(), 0.0f, Color.black); // Dummy
			// object
		}
		else if ("Empty".equals(name)) {
			o = new Empty(new Point());
		}
		else if ("TriggerArea".equals(name)) {
			o = new TriggerArea(new Point(), new Point());
		}
		if (o != null) {
			o.fromFileString(getAttr(item, "data"));
			go.addGraphicsObject(o, getSync(item, form));
			form.transformGraphicsObject(o);
			NamedNodeMap attributes = item.getAttributes();
			String n = getAttr(attributes, "name");
			if (!n.equals(""))
				go.setName(o, n);
			return;
		}
		if ("JSpring".equals(name)) {
			String jstr = getAttr(item, "data");
			String l[] = jstr.split("/");
			Body b0 = bodyIdAssign.get(Integer.valueOf(l[0]));
			Body b1 = bodyIdAssign.get(Integer.valueOf(l[2]));
			Point aoff0 = form.scale(Point.valueOf(l[1]));
			Point aoff1 = form.scale(Point.valueOf(l[3]));
			Point a0 = b0.pos.add(aoff0);
			Point a1 = b1.pos.add(aoff1);
			float strength = Float.valueOf(l[4]);
			float damp = Float.valueOf(l[5]);
			float len = Float.valueOf(l[6]);
			JSpring j = new JSpring(b0, b1, a0, a1, len, strength, damp);
			go.addJoint(j);
			// jointIdAssign.put(Integer.valueOf(getAttr(item.getAttributes(), "id")), j);*/
			NamedNodeMap attributes = item.getAttributes();
			go.setName(j, getAttr(attributes, "name"));
			return;
		}
		if ("JDistanceJoint".equals(name)) {
			String jstr = getAttr(item, "data");
			String l[] = jstr.split("/");
			Body b0 = bodyIdAssign.get(Integer.valueOf(l[0]));
			Body b1 = bodyIdAssign.get(Integer.valueOf(l[2]));
			Point aoff0 = form.scale(Point.valueOf(l[1]));
			Point aoff1 = form.scale(Point.valueOf(l[3]));
			Point a0 = b0.pos.add(aoff0);
			Point a1 = b1.pos.add(aoff1);
			float length = Float.valueOf(l[4]);
			JDistanceJoint j = new JDistanceJoint(b0, b1, a0, a1, length);
			go.addJoint(j);
			// jointIdAssign.put(Integer.valueOf(getAttr(item.getAttributes(), "id")), j);
			NamedNodeMap attributes = item.getAttributes();
			go.setName(j, getAttr(attributes, "name"));
			return;
		}
		assert (false) : "No Implementation for the type " + name;
	}
	private static String getAttr(Node item, String key) {
		return getAttr(item.getAttributes(), key);
	}
	private static GSync getSync(Node item, PointTransformer form) {
		NamedNodeMap attributes = item.getAttributes();
		SyncType type = SyncType.valueOf(getAttr(attributes, "type"));
		int targetId = -1;
		if (type == SyncType.BODY)
			targetId = bodyIdAssign.get(Integer.valueOf(getAttr(attributes, "targetId"))).id;
		if (type == SyncType.JOINT)
			targetId = jointIdAssign.get(Integer.valueOf(getAttr(attributes, "targetId"))).id;
		int groId = Integer.valueOf(getAttr(attributes, "groId"));
		Point offset = form.scale(Point.valueOf(getAttr(attributes, "offset")));
		float angleoffset = Float.valueOf(getAttr(attributes, "angleoffset"));
		return new GSync(type, groId, targetId, offset, angleoffset);
	}
	private static String getAttr(NamedNodeMap attributes, String key) {
		return attributes.getNamedItem(key).getNodeValue();
	}
	private static Body processBody(Node item, GameObject go, PointTransformer form) {
		// Find shape
		NamedNodeMap attributes = item.getAttributes();
		float apos = Float.valueOf(getAttr(attributes, "apos"));
		Shape s = processShape(item, form, apos);
		Body b = new Body(s, 0.0f);
		b.pos = form.transform(Point.valueOf(getAttr(attributes, "pos")));
		b.apos = apos;
		b.friction = Float.valueOf(getAttr(attributes, "friction"));
		b.restitution = Float.valueOf(getAttr(attributes, "restitution"));
		b.unsetStatic(Float.valueOf(getAttr(attributes, "mass")));
		String c = getAttr(attributes, "noncolbodies");
		if (c != "") {
			String cbs[] = c.split(";");
			for (int i = 0; i < cbs.length; i++) {
				b.nonCollidingBodies.add(Integer.valueOf(cbs[i]));
			}
		}
		bodyIdAssign.put(Integer.valueOf(getAttr(attributes, "id")), b);
		return b;
	}
	private static Shape processShape(Node ge, PointTransformer form, float alpha) {
		NodeList children = ge.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			if (children.item(i).getNodeName() == "polygon") {
				return processPolygon(children.item(i), form, alpha);
			}
			else if (children.item(i).getNodeName() == "circle") {
				return processCircle(children.item(i), form);
			}
		}
		assert (false);
		return null;
	}
	private static Shape processCircle(Node item, PointTransformer form) {
		NamedNodeMap attributes = item.getAttributes();
		Point pos = null;
		float radius = Float.NaN;
		for (int i = 0; i < attributes.getLength(); i++) {
			Node k = attributes.item(i);
			String key = k.getNodeName();
			String value = k.getNodeValue();
			if ("pos".equals(key))
				pos = form.transform(Point.valueOf(value));
			if ("radius".equals(key))
				radius = (form.scale.x + form.scale.y) * 0.5f * Float.valueOf(value); // Scale circles with arithmetic
																						// mean of scale vector.
		}
		assert (pos != null && radius != Float.NaN);
		return new Circle(pos, radius);
	}
	private static Shape processPolygon(Node ge, PointTransformer form, float alpha) {
		NodeList children = ge.getChildNodes();
		Vector<Point> points = new Vector<Point>();
		for (int i = 0; i < children.getLength(); i++) {
			if (children.item(i).getNodeName() == "point") {
				Point p = form.transform(readPolygonPointNode(children.item(i)));
				points.add(p);
			}
		}
		Point[] pointArray = new Point[points.size()];
		for (int i = 0; i < points.size(); i++) {
			pointArray[i] = points.get(i);
		}
		return constructPolygon(pointArray, alpha, form);
	}
	private static Polygon constructPolygon(Point[] points, float alpha, PointTransformer form) {
		// The middle point is the position
		Point pos = Point.mean(points);
		Point[] pointsOffset = new Point[points.length];
		for (int i = 0; i < points.length; i++) {
			// for each point : offset is the point minus the position of the
			// body, rotated backwards by the rotation of the body and then
			// scaled and rotated
			// from the outer bounding box?
			pointsOffset[i] = points[i].sub(pos).rotate(-alpha);
		}
		return new Polygon(pos, pointsOffset);
	}
	private static Point readPolygonPointNode(Node item) {
		return Point.valueOf(item.getAttributes().getNamedItem("xy").getNodeValue());
	}
	private static Document getDocumentElement(String fileName) {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		try {
			db = dbf.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return null;
		}
		try {
			Document doc = db.parse(fileName);
			return doc;
		} catch (Exception e) {
			// e.printStackTrace();
			Log.f("wrong filename? " + fileName);
		}
		return null;
	}
}
